﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;

namespace ABFramework
{
    /// <summary>
    /// 资源包加载管理器
    /// </summary>
    public class AssetBundleManager : MonoBehaviour
    {
        #region 实现AssetBundleManager类的单例
        private static AssetBundleManager _instance;
        private AssetBundleManager() { }
        public static AssetBundleManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = GameInit.GameManager.AddComponent<AssetBundleManager>();
                }
                return _instance;
            }
        }
        #endregion

        /// <summary>
        /// 初始化
        /// </summary>
        public void Init()
        {
            //当前运行平台根目录
            _PlatformRootPath = PathTools.GetPlatformABRootPath();
            if (GameInit.Instance._EnabledHotUpdate)
            {
                //获取资源校验清单文件
                string verifyPath = _PlatformRootPath + "/" + PathTools.VERYIFY_FILE_NAME;
                string verify_text = File.ReadAllText(verifyPath);
                VerifyTxtArray = verify_text.Split('\n');
            }
            //运行平台
            runtimePlatform = PathTools.GetRuntimePlatformName();
            //加载总manifest文件
            StartCoroutine(LoadTotalManifest());
        }
        /*所有(任何一个)被加载的资源包保存的集合*/
        public Dictionary<string, AssetBundle> _DicAllModuleAssetBundle = new Dictionary<string, AssetBundle>();

        /*场景模块加载的资源包列表，可被释放*/
        public List<string> _SceneLoaddedABList = new List<string>();

        //依赖清单
        Dictionary<string, List<string>> _PackageRelyOnList = new Dictionary<string, List<string>>();

        //当前平台总清单文件信息,可用来获取指定Assetbundle的依赖资源名
        AssetBundleManifest _TotalManifest = null;
        AssetBundle _TotalManifestAB = null;
        bool _LoadTotalManifestFinished = false;
        //存放校验文件的数组
        string[] VerifyTxtArray;
        //当前运行平台名字
        string runtimePlatform;

        //平台资源加载的根目录
        string _PlatformRootPath;

        /// <summary>
        /// 获取某个模块下所有的ab资源路径
        /// </summary>
        /// <param name="form_path">查询的目录</param>
        /// <returns></returns>
        private void GetModuleAllAssetBundlePath(string moduleName, ref List<string> ret_list)
        {
            //拿到校验文件的每一行（数组最后1个不是校验数据【是统计信息】）
            for (int i = 0, real_len = VerifyTxtArray.Length - 1; i < real_len; i++)
            {
                string[] line_info = VerifyTxtArray[i].Split('|');
                //数组长度不为3则说明该行不是校验数据【名字|MD5|大小】,或者不是指定模块资源则跳过
                if (!VerifyTxtArray[i].StartsWith(runtimePlatform + "/" + moduleName.ToLower()) || line_info.Length != 3) continue;

                string abPath = PathTools.GetABRootPath() + "/" + line_info[0];
                //Debug.Log("添加:" + abPath);
                ret_list.Add(abPath);
            }
        }

        #region 资源加载方式，供外部调用

        /// <summary>
        /// 加载所有的公用资源包
        /// </summary>
        /// <param name="cb">所有公共资源包加载完成后的回调</param>
        public void LoadAllCommonBundle(Action cb = null)
        {
#if UNITY_EDITOR
            //在编辑器模式下且未开启热更新则直接在本地读取,而不是从资源下载路径读取
            if (!GameInit.Instance._EnabledHotUpdate)
            {
                //string str = "<color=red>In editor mode , not open hotupdate features! No need to load resources!</color>";
                //Debug.Log(str);
                cb?.Invoke();
            }
            else
            {
                StartCoroutine(IELoadCommonAssetBundleByModule(cb));
            }
#else
                StartCoroutine(IELoadCommonAssetBundleByModule(cb));
#endif
        }

        /// <summary>
        /// 加载一个数组中的资源包
        /// </summary>
        /// <param name="relative_path"></param>
        /// <param name="isCommon">是否为公共资源</param>
        /// <param name="cb"></param>
        /// <param name="isScene">是否为场景</param>
        public void LoadBundleByList(List<string> relative_path, Action cb = null, bool isCommon = false, bool isScene = false)
        {
#if UNITY_EDITOR
            //在编辑器模式下且未开启热更新则直接在本地读取,而不是从资源下载路径读取
            if (!GameInit.Instance._EnabledHotUpdate)
            {
                //string str = "<color=red>In editor mode , not open hotupdate features! No need to load resources!</color>";
                //Debug.Log(str);
                cb?.Invoke();
            }
            else
            {

                StartCoroutine(IELoadSingleBundleByList(relative_path, cb, isCommon, isScene));
            }
#else
                StartCoroutine(IELoadSingleBundleByList(relative_path, cb,isCommon, isScene));
#endif
        }

        /// <summary>
        /// 执行加载一个或多个模块资源
        /// <param name="public_mode">加载的是否为公用资源</param>
        /// <param name="moudles">需要加载资源的模块名数组</param>
        /// <param name="progress_cb">加载的总进度回调</param>
        /// <returns></returns>
        public IEnumerator DoLoadModules(bool public_mode = false, Action<int, int> progress_cb = null, params string[] moudles)
        {
            if (moudles == null || !GameInit.Instance._EnabledHotUpdate)
            {
                Debug.Log("直接进行回调!资源未加载!!" + moudles);
                progress_cb?.Invoke(1, 1);
                yield break;
            }
            //存放需要加载所有包的地址
            List<string> all_ab_list = new List<string>();

            //遍历需要加载的每个模块，然后加载其下的资源包
            foreach (var module_item in moudles)
            {
                //公用模块要按功能加载资源，最好不要一口气加载完成。
                if (module_item == "Common")
                {
                    Debug.LogError("提示：公用模块尽量不要直接加载,而是分成功能加载!!!");
                }
                //得到该模块所有的资源包的加载地址
                GetModuleAllAssetBundlePath(module_item, ref all_ab_list);
            }
            //加载进度记录
            int count = 0;
            int total_count = all_ab_list.Count;
            //遍历加载
            foreach (var ab_path in all_ab_list)
            {
                count++;
                //执行进度回调
                progress_cb?.Invoke(count, total_count);
                //加载该指定地址的资源包
                yield return DoLoadSingleAssetBundle(ab_path, public_mode);
            }
        }

        #endregion

        /// <summary>
        /// 加载数组中的单个资源包
        /// </summary>
        /// <param name="relative_path"></param>
        /// <param name="isCommon">是否为公共资源</param>
        /// <param name="cb">回调</param>
        /// <param name="isScene">是否为场景资源</param>
        private IEnumerator IELoadSingleBundleByList(List<string> relative_path, Action cb, bool isCommon, bool isScene)
        {
            foreach (var path in relative_path)
            {
                if (isScene)
                {
                    yield return DoLoadSingleAssetBundle(_PlatformRootPath + "/" + path.ToLower() + ".scene", isCommon);
                }
                else
                {
                    yield return DoLoadSingleAssetBundle(_PlatformRootPath + "/" + path.ToLower() + ".ab", isCommon);
                }
            }
            cb?.Invoke();
        }
        /// <summary>
        /// 2.加载整个公用（基础）资源包，即Common模块
        /// </summary>
        /// <param name="relative_path"></param>
        /// <param name="cb">回调</param>
        private IEnumerator IELoadCommonAssetBundleByModule(Action cb)
        {
            if (!GameInit.Instance._EnabledHotUpdate)
            {
                Debug.Log("加载基础资源包:未开启热更模式,不需要加载资源包!");
                cb?.Invoke();
                yield break;
            }
            List<string> all_ab_list = new List<string>();

            GetModuleAllAssetBundlePath("Common", ref all_ab_list);

            foreach (var abPath in all_ab_list)
            {
                yield return DoLoadSingleAssetBundle(abPath, true);
            }

            //执行回调
            cb?.Invoke();
        }

        /// <summary>
        /// 加载某个AssetBundle包
        /// </summary>
        /// <param name="full_name">ab包全路径</param>
        /// <param name="public_nodule">是否为公用资源</param>
        /// <returns></returns>
        IEnumerator DoLoadSingleAssetBundle(string full_name, bool public_nodule = false)
        {
            full_name = full_name.Replace("\\", "/");
            //ab资源相对平台根目录的位置
            string relative_path = (full_name.Replace(_PlatformRootPath + "/", "")).ToLower();

            //等待中的资源依赖清单加载完成才进行资源包加载
            while (!_LoadTotalManifestFinished)
            {
                //Debug.Log("Must Wait '_LoadTotalManifestFinished=true' Current Load："+ relative_path);
                yield return 0;
            }

            //得到该资源的的所有依赖资源名称
            string[] all_dependencies = _TotalManifest.GetAllDependencies(relative_path);

            //如果有依赖资源，递归本方法先进行依赖资源的加载
            foreach (var dependency_rpath in all_dependencies)
            {
                //将依赖资源引用关系保存
                if (_PackageRelyOnList.ContainsKey(dependency_rpath))
                {
                    _PackageRelyOnList[dependency_rpath].Add(relative_path);
                }
                else
                {
                    _PackageRelyOnList.Add(dependency_rpath, new List<string>() { relative_path });
                }
                //Debug.Log("LOAD:" + relative_path + " dependency assetbundle：" + dependency_rpath);
                yield return DoLoadSingleAssetBundle(_PlatformRootPath + "/" + dependency_rpath, public_nodule);
            }

            /**************************************************/
            //检测是否为公用资源,非公用资源需要保存起来，方便一起释放掉
            if (!public_nodule && !_SceneLoaddedABList.Contains(relative_path))
            {
                //Debug.Log("LOAD:(Scene) " + relative_path);
                _SceneLoaddedABList.Add(relative_path);
            }
            //检测是否已经架加载过该资源包，加载过则不加载。
            if (_DicAllModuleAssetBundle.ContainsKey(relative_path))
            {
                yield break;
            }

            //加载本地资源包
            AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(full_name);
            yield return request;
            if (request.isDone)
            {
                AssetBundle bundle = request.assetBundle;
                //Debug.Log("LOADAB: C(" + public_nodule + "):" + relative_path);
                //将资源包加入集合缓存起来
                _DicAllModuleAssetBundle.Add(relative_path, bundle);
            }

        }

        /// <summary>
        /// 获取当前平台总清单文件信息
        /// </summary>
        /// <returns></returns>
        IEnumerator LoadTotalManifest()
        {
            //如果不开启热更，则没必要加载此对象清单文件
            if (!GameInit.Instance._EnabledHotUpdate)
            {
                yield break;
            }
            //当前平台最外层ab包(Windows/iOS/Android)的全路径
            string full_name = PathTools.GetPlatformABRootPath() + "/" + PathTools.GetRuntimePlatformName();
            //Debug.Log("加载资源包：" + full_name);

            //加载本地资源包
            AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(full_name);

            yield return request;
            if (request.isDone)
            {
                _TotalManifestAB = request.assetBundle;
                _TotalManifest = _TotalManifestAB.LoadAsset("AssetBundleManifest") as AssetBundleManifest;

                if (_TotalManifest == null)
                {
                    Debug.LogError("当前平台的总清单文件不存在!请确认! " + full_name);
                    Debug.LogError("AssetBundleManager Init Failed!!!");
                }
                else
                {
                    _LoadTotalManifestFinished = true;
                    _DicAllModuleAssetBundle.Add(PathTools.GetRuntimePlatformName(), _TotalManifestAB);
                    Debug.Log("AssetBundleManager Init!");
                }
            }
        }

        /// <summary>
        /// 查询某个资源包是否已经没有任何引用了
        /// </summary>
        /// <returns></returns>
        bool UnLoadCheckHaveNoRely(string name)
        {
            //要卸载的资源包被其他资源包依赖
            if (_PackageRelyOnList.ContainsKey(name))
            {
                //拿到所有依赖该资源包的所有资源包列表
                List<string> rely_list = _PackageRelyOnList[name];
                List<string> remove_list = new List<string>();
                //遍历依赖该依赖包的资源包的集合
                foreach (var item in rely_list)
                {
                    //检测每一个依赖项,如果该依赖项在被卸载的集合中,则移除
                    if (_SceneLoaddedABList.Contains(item))
                    {
                        remove_list.Add(item);
                    }
                }
                //移除
                foreach (var item in remove_list)
                {
                    rely_list.Remove(item);
                }

                //如果还有依赖则不卸载,否则可以卸载
                if (rely_list.Count == 0)
                {
                    return true;
                }
                else
                {
                    Debug.Log("还有资源在引用【" + name + "】跳过卸载！");
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 释放进入场景中的(非公用资源列表中)的所有资源
        /// </summary>
        public void UnLoadAllSceneAssetBundles()
        {
            List<string> remove_list = new List<string>();
            //遍历释放当前AB集合的所有包
            foreach (var name in _SceneLoaddedABList)
            {
                if (UnLoadCheckHaveNoRely(name))
                {
                    remove_list.Add(name);
                    //该资源包无任何引用，可以卸载
                    UnLoadSingleAssetBundle(name);
                }
            }
            //移除保存所有资源的字典中的该资源包
            foreach (var item in remove_list)
            {
                _DicAllModuleAssetBundle.Remove(item);
            }
            //释放所有没有被使用的资源
            Resources.UnloadUnusedAssets();
        }


        /// <summary>
        /// 释放所有加载过的资源包（所有！！！）
        /// </summary>
        public void UnloadAllLoadedAssetBundles()
        {
            //Debug.Log("卸载所有(任何一个)被加载的资源包!");
            //遍历释放当前AB集合的所有包
            foreach (var name in _DicAllModuleAssetBundle.Keys)
            {
                UnLoadSingleAssetBundle(name);
            }
            //清理保存数据的字典
            _DicAllModuleAssetBundle.Clear();
            _DicAllModuleAssetBundle = new Dictionary<string, AssetBundle>();
            //释放所有没有被使用的资源
            Resources.UnloadUnusedAssets();
        }

        /// <summary>
        /// 释放单个指定的加载的资源包
        /// </summary>
        /// <param name="name">资源包相对路径</param>
        /// <param name="dispose_public_asset">是否卸载的是公用资源</param>
        public void UnLoadSingleAssetBundle(string name)
        {
            //彻底卸载该资源包
            if (_DicAllModuleAssetBundle.ContainsKey(name))
            {
                //Debug.Log(name + "资源包被卸载!!!");
                _DicAllModuleAssetBundle[name].Unload(true);
            }
            else
            {
                Debug.Log(name + "要卸载的资源包不存在!!!");
            }
        }

        /// <summary>
        /// 获取所有被加载的AB的名字（包括公共资源）
        /// </summary>
        /// <returns></returns>
        public void GetAllLoadedABNameList()
        {
            string info = "缓存的资源包共【" + (_DicAllModuleAssetBundle.Count) + "】个";

            foreach (var item in _DicAllModuleAssetBundle.Keys)
            {
                if (_SceneLoaddedABList.Contains(item))
                {
                    info += "\n[Scene] - " + item;
                }
                else
                {
                    info += "\n[Common] - " + item;
                }
            }
            Debug.Log(info);
        }

        /// <summary>
        /// 打印依赖关系
        /// </summary>
        /// <returns></returns>
        public void PrintRelayList()
        {
            int count_out = 0;
            string info = "资源包依赖关系如下";

            List<string> array_list = new List<string>();

            foreach (var name in _PackageRelyOnList.Keys)
            {
                count_out++;
                int count_inner = 0;
                info += "\n" + count_out + ".[♦被依赖] - " + name;
                foreach (var item in _PackageRelyOnList[name])
                {
                    count_inner++;
                    info += "\n\t" + count_inner + ".[♢依赖者] - " + item;
                }
            }
            Debug.Log(info);
        }
    }

}
